using System.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using UMC.Net;

namespace UMC.Host
{



    public class HttpWebSocket : UMC.Net.Mime, IDisposable
    {
        public override bool IsWebSocket => true;
        NetWriteData _writer;
        Action _close;
        Guid _device;
        public HttpWebSocket(NetWriteData writeData, Guid device, Action close, ByteChunk memory) : base(memory)
        {
            this._writer = writeData;
            this._close = close;
            this._device = device;
            if (_WebSockets.TryRemove(device, out var webSocket))
            {
                webSocket.Dispose();
            }
            this.ActiveTime = UMC.Data.Utility.TimeSpan();
            _WebSockets[_device] = this;
            this.ReceiveData = (b, c, l) => { };
            this.ReceiveText = (b, c, l) => { };
        }
        public Action Close
        {
            get; set;
        }
        public NetWriteData ReceiveData
        {
            get; set;
        }

        public NetWriteData ReceiveText
        {
            get; set;
        }
        public void Disconnect()
        {
            var bs = System.Buffers.ArrayPool<byte>.Shared.Rent(10);
            try
            {
                bs[0] = 0x88;
                bs[1] = 0x04;
                var len = "close".WriteBytes(bs, 2) + 2;

                this._writer(bs, 0, len);
                this.Dispose();
            }
            catch
            {
                this.Dispose();
            }
            finally
            {
                System.Buffers.ArrayPool<byte>.Shared.Return(bs);
            }

        }
        public static bool Online(Guid k)
        {
            return _WebSockets.ContainsKey(k);
        }
        public static bool TryGet(Guid k, out HttpWebSocket socket)
        {
            return _WebSockets.TryGetValue(k, out socket);
        }
        public static void Send(Guid device, String msg)
        {
            if (_WebSockets.TryGetValue(device, out var webSocket))
            {
                webSocket.Send(msg);
            }
            ;
        }
        public uint ActiveTime
        {

            get; private set;
        }
        public static HttpWebSocket[] WebSockets
        {
            get
            {
                return _WebSockets.Values.ToArray();
            }
        }

        static ConcurrentDictionary<Guid, HttpWebSocket> _WebSockets = new ConcurrentDictionary<Guid, HttpWebSocket>();

        public void Ping()
        {
            var bs = System.Buffers.ArrayPool<byte>.Shared.Rent(10);
            try
            {
                bs[0] = 0x89;
                bs[1] = 0x04;
                var len = "ping".WriteBytes(bs, 2) + 2;

                this._writer(bs, 0, len);
            }
            finally
            {
                System.Buffers.ArrayPool<byte>.Shared.Return(bs);
            }
        }
        public void Send(String msg)
        {
            int count = System.Text.Encoding.UTF8.GetByteCount(msg);
            var bs = System.Buffers.ArrayPool<byte>.Shared.Rent(count);
            System.Text.Encoding.UTF8.GetBytes(msg, 0, msg.Length, bs, 0);
            try
            {
                Send(0x81, bs, 0, count);
            }
            finally
            {

                System.Buffers.ArrayPool<byte>.Shared.Return(bs);
            }

        }
        protected void Send(byte type, byte[] bytes, int offset, int count)
        {
            var bs = System.Buffers.ArrayPool<byte>.Shared.Rent(6 + count);
            try
            {
                bs[0] = type;
                int size = 2;

                if (count < 126)
                {
                    bs[1] = (byte)count;
                }
                else if (count < UInt16.MaxValue)
                {
                    bs[1] = 126;
                    UInt16 v = (UInt16)count;
                    BitConverter.TryWriteBytes(bs.AsSpan(2, 2), v);
                    Array.Reverse(bs, 2, 2);
                    size = 4;
                }
                else
                {
                    size = 10;
                    bs[1] = 127;
                    ulong v = (ulong)count;
                    BitConverter.TryWriteBytes(bs.AsSpan(2, 8), v);
                    Array.Reverse(bs, 2, 8);
                }
                this.ActiveTime = UMC.Data.Utility.TimeSpan();
                Array.Copy(bytes, offset, bs, size, count);
                this._writer(bs, 0, count + size);

            }
            finally
            {
                System.Buffers.ArrayPool<byte>.Shared.Return(bs);
            }
        }
        public void Send(byte[] bytes)
        {
            Send(0x82, bytes, 0, bytes.Length);

        }
        public void Send(byte[] bytes, int offset, int count)
        {
            Send(0x82, bytes, offset, count);

        }
        public override int Receive(byte[] buffer, int offset, int size)
        {
            this.ActiveTime = UMC.Data.Utility.TimeSpan();
            return Frame(buffer, offset, size);
        }
        int receSize = 0;
        int receIndex = 0;
        bool isMask;
        byte[] mask = new byte[4];
        byte opcode;

        int Frame(byte[] buffer, int offset, int size)
        {
            int end = offset + size;
            while (offset < end)
            {
                if (receSize > 0)
                {
                    if (isMask)
                    {
                        for (uint i = 0; i < receSize; i++)
                        {
                            buffer[offset + i] ^= mask[(i + receIndex) % 4];
                        }
                    }
                    int sendSize = end - offset;
                    if (sendSize > receSize)
                    {
                        sendSize = receSize;
                    }
                    receSize -= sendSize;
                    receIndex += sendSize;
                    switch (opcode)
                    {
                        case 0x00:
                            this.ReceiveData(buffer, offset, sendSize);
                            break;
                        case 0x01:
                            this.ReceiveText(buffer, offset, sendSize);
                            break;
                        case 0x02:
                            this.ReceiveData(buffer, offset, sendSize);
                            break;
                        case 0x08:
                            this.Dispose();
                            break;
                        case 0x89:
                        case 0x09:
                            // this.ReceiveText(buffer, offset, sendSize);
                            break;
                        case 0x0A:
                            // this.Ping();
                            break;
                    }
                    offset += sendSize;

                }
                else if (end - offset > 2)
                {
                    size = end - offset;
                    opcode = buffer[offset];//&0xf;
                    opcode <<= 4;
                    opcode >>= 4;
                    byte len = buffer[offset + 1];
                    offset += 2;
                    isMask = len >> 7 == 1;
                    int minSize = 2;
                    if (isMask)
                    {
                        minSize += 4;
                    }
                    if (minSize > size)
                    {
                        return size;
                    }
                    len <<= 1;
                    len >>= 1;
                    receSize = len;//<<= 1;
                    receIndex = 0;
                    switch (len)
                    {
                        case 126:
                            minSize += 2;
                            if (minSize > size)
                            {
                                return size;
                            }
                            Array.Reverse(buffer, offset, 2);
                            receSize = BitConverter.ToUInt16(buffer, offset);
                            offset += 2;
                            break;
                        case 127:

                            minSize += 8;
                            if (minSize > size)
                            {
                                return size;
                            }
                            // start += 8;
                            Array.Reverse(buffer, offset, 8);
                            receSize = Convert.ToInt32(BitConverter.ToUInt64(buffer, offset));
                            offset += 8;
                            break;
                    }
                    if (isMask)
                    {
                        Array.Copy(buffer, offset, mask, 0, 4);
                        offset += 4;

                    }
                }
                else
                {
                    break;
                }
            }
            return end - offset;
        }


        bool IsDispose = false;
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (IsDispose == false)
            {
                if (Close != null)
                {
                    Close();
                }
                IsDispose = true;
                _WebSockets.TryRemove(this._device, out var _);

                _close();
            }
        }
    }


}

